#include "TokenClassMetaManager.h"

#include <MOE/Core/BasicTypes.h>
#include <MOE/Core/ClassMeta.h>
#include <MOE/System/ClassLibrary.h>

#include <cassert>

namespace MOE
{
	namespace System
	{
		namespace NonIntrusive
		{

			// This singleton has to be manually initialized/released by calling InitializeSingleton()/ReleaseSingleton().
			// This gives us better control of the singleton's lifetime.
			TokenClassMetaManager* TokenClassMetaManager::sInstance = nullptr;

			void TokenClassMetaManager::InitializeSingleton()
			{
				if (SingletonInitialized())
				{
					return;
				}

				sInstance = new TokenClassMetaManager();
			}

			void TokenClassMetaManager::ReleaseSingleton()
			{
				delete sInstance;
				sInstance = nullptr;
			}

			bool TokenClassMetaManager::SingletonInitialized()
			{
				return (sInstance != nullptr);
			}

			TokenClassMetaManager* TokenClassMetaManager::GetInstance()
			{
				return sInstance;
			}


			TokenClassMetaManager::TokenClassMetaManager()
				: mClassMetaMap()
				, mLibrary(new ClassLibrary())
			{
				InitializeBaseTypes();
			}

			TokenClassMetaManager::~TokenClassMetaManager()
			{
				// No need to delete the ClassMeta* pointers in mClassMetaMap, as their lifetimes are managed by mLibrary, which will be deleted anyway.
			}

			// Creates a class meta. This pointer is owned by mLibrary and should thus only be used to initialize the rest of the class meta structure.
			Core::ClassMeta* TokenClassMetaManager::CreateClassMeta(tTokenType classType, tTokenType parentType, const Core::char8* className, Core::tSize elementSize)
			{
				// Validate if the class is duplicated.
				if (mClassMetaMap.find(classType) != mClassMetaMap.end())
				{
					return nullptr;
				}

				// Validate if the class name is duplicated.
				if (!mLibrary->GetClassMeta(className).IsNil())
				{
					return nullptr;
				}

				Core::ClassMeta* pMeta = nullptr;
				bool hasParent = parentType != tTokenType();
				if (hasParent)
				{
					const Core::ClassMeta& parentMeta = GetClassMeta(parentType);
					if (parentMeta.IsNil())
					{
						hasParent = false;
					}
					else
					{
						// Create a class meta with parent.
						pMeta = new Core::ClassMeta(parentMeta, className, elementSize);
					}
				}
				if (!hasParent)
				{
					assert(pMeta == nullptr);
					// Create a class meta without parent.
					pMeta = new Core::ClassMeta(className, elementSize);
				}
				assert(pMeta != nullptr);
				mClassMetaMap[classType] = pMeta;
				mLibrary->AddClassMeta(pMeta);
				return pMeta;
			}

			const Core::ClassMeta& TokenClassMetaManager::GetClassMeta(tTokenType tokenValue) const
			{
				auto iter = mClassMetaMap.find(tokenValue);
				if (iter == mClassMetaMap.end())
				{
					return Core::ClassMeta::kNil;
				}
				return *iter->second;
			}

			void TokenClassMetaManager::DestroyClassMeta(tTokenType classType)
			{
				auto iter = mClassMetaMap.find(classType);
				if (iter == mClassMetaMap.end())
				{
					return;
				}
				mLibrary->RemoveClassMeta(iter->second->GetClassName());
				mClassMetaMap.erase(iter);
			}

			// Immediately creates class meta for basic types as soon as the TokenClassMetaManager is created.
			void TokenClassMetaManager::InitializeBaseTypes()
			{
				CreateBasicTypeClassMeta<Core::int8>(Core::BasicType::kInt8, "Byte");
				CreateBasicTypeClassMeta<Core::uint8>(Core::BasicType::kUint8, "UnsignedByte");
				CreateBasicTypeClassMeta<Core::int16>(Core::BasicType::kInt16, "Short");
				CreateBasicTypeClassMeta<Core::uint16>(Core::BasicType::kUint16, "UnsignedShort");
				CreateBasicTypeClassMeta<Core::int32>(Core::BasicType::kInt32, "Int");
				CreateBasicTypeClassMeta<Core::uint32>(Core::BasicType::kUint32, "UnsignedInt");
				CreateBasicTypeClassMeta<Core::int64>(Core::BasicType::kInt64, "Long");
				CreateBasicTypeClassMeta<Core::uint64>(Core::BasicType::kUint64, "UnsignedLong");
				CreateBasicTypeClassMeta<Core::float32>(Core::BasicType::kFloat32, "Float");
				CreateBasicTypeClassMeta<Core::float64>(Core::BasicType::kFloat64, "Double");
			}

			// Helper class to create basic type class meta.
			template <class T>
			void TokenClassMetaManager::CreateBasicTypeClassMeta(Core::BasicType basicTypeEnum, const Core::char8* basicTypeName)
			{
				Core::ClassMeta* pMeta = new Core::ClassMeta(basicTypeEnum, basicTypeName, T());
				mClassMetaMap[TypeToken<T>::Value()] = pMeta;
				mLibrary->AddClassMeta(pMeta);
			}

		}
	}
}
